<cfcomponent displayname="FusecacheFactory" output="false" hint="">
	
	<cfset variables.instance = {} />
	
	<!--- init() --->
	<cffunction name="init" displayname="init" access="public" output="false" returntype="fusecache.FusecacheFactory" hint="Initializes the Fusecache Factory by creating local instance scopes, loading the caching engine, and by reading, parsing, and storing the configuration data from the configuration XML.">
		<cfargument name="sConfigFileLocation" type="string" required="true">
		<cfargument name="oCacheEngine" type="any" required="true" />
		<cfargument name="sGlobalKeyPrefix" type="string" required="false" default="" />
		<cfset var local = {} />

		<cfscript>
			variables.instance["sConfigFilePath"] = arguments.sConfigFileLocation; // file path to the config
			variables.instance["oCacheEngine"] = arguments.oCacheEngine; // Save cache engine to factory instance
			variables.instance["sGlobalKeyPrefix"] = arguments.sGlobalKeyPrefix;
			loadConfig(arguments.sConfigFileLocation); // Pull config file into memory from disk (does not parse)
			readSettings(); // Parse and process the <settings> section	
			buildCacheMap(); // Parse and process the <items> section
			if (doWriteStaticFiles()) {
					variables.instance["oStaticFileManager"] = CreateObject("component","fusecache.FusecacheStaticFileManager").init(this);
			}
			return this;
		</cfscript>

	</cffunction>
	
	
	<!--- refresh() --->
	<cffunction name="refresh" displayname="refresh" access="public" output="false" returntype="boolean" hint="Refreshes the Fusecache Factory by re-reading in the configuration XML and overriding the existing settings with the new data.">
		<cfscript>
			loadConfig(arguments.sConfigFileLocation);
			buildCacheMap();
			return true;
		</cfscript>
	</cffunction>
	
	
	<!--- shutdown() --->
	<cffunction name="shutdown" access="public" output="false" returntype="void" hint="Manages processes (mostly cleanup) associated with restarting shutting down the application (timeout).">
		<cfif (doWriteStaticFiles())>
			<cfset variables.instance["oStaticFileManager"].shutdown() />
		</cfif>	
	</cffunction>


	<!--- loadConfig() --->
	<cffunction name="loadConfig" access="private" output="false" returntype="boolean" hint="Handles file system operations for reading in XML configuration file, parsing that file into a ColdFusion XML object, and storing that object within the factory instance.">
		<cfargument name="sConfigFileLocation" type="string" required="true">
		<cfset var local = {} />
		
		<!--- Attempt to read in the file --->
		<cftry>
			<cffile action="read" file="#ExpandPath(arguments.sConfigFileLocation)#" variable="variables.instance.configFile" />
			<cfcatch type="any">
				<cfthrow type="fusecache.missingConfig" message="We were unable to locate the file: #ExpandPath(arguments.sConfigFileLocation)#" />
			</cfcatch>
		</cftry>
		
		<!--- Attempt to read in the XSD definition which should be in same folder --->
		<cftry>
			<cfset Local.sXSDLocation = GetDirectoryFromPath(GetCurrentTemplatePath()) & "config.xsd" />
			<cffile action="read" file="#local.sXSDLocation#" variable="variables.instance.xsd" />
			<cfcatch type="any">
				<cfthrow type="fusecache.missingXSD" message="The XML definition file could not be found here: #Local.sXSDLocation#" />
			</cfcatch>
		</cftry>	
		
		<!--- Confirm the file is formatted correctly --->
		<cftry>
			<cfset local.xmlResult = XmlParse(REReplace(getConfig(),"^[^<]*","","all")) />
			<cfset local.stValidationResults = XmlValidate(local.xmlResult,local.sXSDLocation) />
			<cfif (local.stValidationResults.Status neq "YES")>
				<cfreturn false />
			</cfif>
			<cfcatch type="any">
				<cfthrow type="fusecache.invalidXSD" message="The XML configuration file was compared to the " />
			</cfcatch>
		</cftry>
		
		<cfreturn true />
	</cffunction>
	
	
	<!--- readSettings() --->
	<cffunction name="readSettings" access="private" output="false" returntype="boolean" hint="Reads in the settings portion of the XML config file and sets object properties accordingly.">
		<cfset var local = {} />

		<!--- Load in config file and grab the settings section --->
		<cfset local.xmlData =  XmlParse(getConfig()) />
		<cfset local.xmlSettings = local.xmlData.fusecache.settings />
		
		<!--- Loop over the children of the <settings> element, putting them into our instance --->
		<cfloop from="1" to="#ArrayLen(local.xmlSettings.xmlChildren)#" index="i">
			<cfset variables.instance["#local.xmlSettings.xmlChildren[i].xmlAttributes.name#"] = local.xmlSettings.xmlChildren[i].xmlText />
		</cfloop>
		
		<!--- Validate settings --->
		<!--- Require useOutputFuse --->
		<cfif (NOT StructKeyExists(variables.instance,"useOutputFuse") OR variables.instance["useOutputFuse"] EQ "")>
			<cfthrow type="fusecache.missingSetting" message="'useOutputFuse' is a required setting in your XML config file. It is either not present or blank." />
		</cfif>
		
		<!--- Require showDebug --->
		<cfif (NOT StructKeyExists(variables.instance,"showDebug") OR variables.instance["showDebug"] EQ "")>
			<cfthrow type="fusecache.missingSetting" message="'showDebug' is a required setting in your XML config file. It is either not present or blank.  Valid values: 'true' or 'false'" />
		</cfif>
		
		<!--- Require staticFileOutput --->
		<cfif (NOT StructKeyExists(variables.instance,"staticFileOutput") OR variables.instance["staticFileOutput"] EQ "")>
			<cfthrow type="fusecache.missingSetting" message="'staticFileOutput' is a required setting in your XML config file. It is either not present or blank.  Valid values: 'true' or 'false'" />
		</cfif>
		
		<!--- Make sure that if useOutputFuse is true, one is provided --->
		<cfif (variables.instance["useOutputFuse"] AND (NOT StructKeyExists(variables.instance,"outputFuse") OR variables.instance["outputFuse"] EQ ""))>
			<cfthrow type="fusecache.NoValidOutputFuse" message="'useOutputFuse' (in your XML config) has been set to true, but you have not provided a valid setting for the 'outputFuse' setting.  Either the element has been left out completely, or the value is empty" />
		</cfif>
		
		<!--- Make sure that if useOutputFuse is true, a content variable is provided --->
		<cfif (variables.instance["useOutputFuse"] AND (NOT StructKeyExists(variables.instance,"outputVariable") OR variables.instance["outputVariable"] EQ ""))>
			<cfthrow type="fusecache.NoValidOutputVariable" message="'useOutputFuse' (in your XML config) has been set to true, but you have not provided a valid setting for the 'outputVariable' setting.  Either the element has been left out completely, or the value is empty" />
		</cfif>

		<cfreturn true />
	</cffunction>
	
	
	<!--- buildCacheMap() --->
	<cffunction name="buildCacheMap" access="private" output="false" returntype="boolean" hint="Translates the XML object created from the configuration file into a native ColdFusion structure to allow for simpler key-based search and access to the data.">
		<cfset var local = {} />
		<cfset local.xmlData = XmlParse(getConfig()) />
		<cfset local.stCacheMap = StructNew() />
		
		<cftry>
			
			<!--- Loop over each <item> element --->
			<cfloop from="1" to="#ArrayLen(Local.XmlData.Fusecache.Items.XmlChildren)#" index="a">
			
				<!--- Loop over the children elements of the current <item> element --->
				<cfloop from="1" to="#ArrayLen(Local.XmlData.Fusecache.Items.XmlChildren[a].XmlChildren)#" index="b">
					
					<!--- Pull out what our current circuit and fuse are --->
					<cfset local.sCurrentCircuit = ListGetAt(local.xmlData.Fusecache.Items.XmlChildren[a].XmlChildren[1].XmlText,1,".") />
					<cfset local.sCurrentFuse = ListGetAt(local.xmlData.Fusecache.Items.XmlChildren[a].XmlChildren[1].XmlText,2,".") />
					
					<!--- Make sure we don't already have this circuit/fuse combination in the map --->					
					<cfif (NOT isDuplicateSetting(local.xmlData,local.sCurrentCircuit,local.sCurrentFuse))>
						
						<cfset local.sCurrentProperty = Local.XmlData.Fusecache.Items.XmlChildren[a].XmlChildren[b].XmlName />
						
						<!--- Set basic values --->
						<cfif (ListFindNoCase("key,expires,type,staticfile",local.sCurrentProperty))>
							<cfset local.sCurrentPropertyValue = Local.XmlData.Fusecache.Items.XmlChildren[a].XmlChildren[b].XmlText />
							<cfif (local.sCurrentProperty EQ "key") AND (getGlobalKeyPrefix() NEQ "")>
								<cfset local.sCurrentPropertyValue = getGlobalKeyPrefix() & "_" & local.sCurrentPropertyValue />
							</cfif>
							<cfset "local.stCacheMap.#local.xmlData.Fusecache.Items.XmlChildren[a].XmlChildren[1].XmlText#.#local.sCurrentProperty#" = local.sCurrentPropertyValue />
						</cfif>
						
						<!--- Set values for conditions/variables --->
						<cfif (ListFindNoCase("conditions,variables",local.sCurrentProperty))>
							
							<cfset local.arConditions = ArrayNew(2) />
							<cfif (local.xmlData.Fusecache.Items.XmlChildren[a].XmlChildren[b].XmlChildren[1].XmlName EQ "condition")>
								<cfloop from="1" to="#ArrayLen(local.XmlData.Fusecache.Items.XmlChildren[a].XmlChildren[b].XmlChildren)#" index="c">
									<cfset local.arConditions[c][1] = local.xmlData.Fusecache.Items.XmlChildren[a].XmlChildren[b].XmlChildren[c].XmlText />
								</cfloop>
								<cfset "Local.stCacheMap.#Local.XmlData.Fusecache.Items.XmlChildren[a].XmlChildren[1].XmlText#.Conditions" = local.arConditions />
							</cfif>
							
							<cfif (local.XmlData.Fusecache.Items.XmlChildren[a].XmlChildren[b].XmlChildren[1].XmlName EQ "variable")>
								<cfloop from="1" to="#ArrayLen(Local.XmlData.Fusecache.Items.XmlChildren[a].XmlChildren[b].XmlChildren)#" index="c">
									<cfset local.arConditions[c][1] = local.xmlData.Fusecache.Items.XmlChildren[a].XmlChildren[b].XmlChildren[c].XmlText />
									<cfset local.arConditions[c][2] = local.xmlData.Fusecache.Items.XmlChildren[a].XmlChildren[b].XmlChildren[c].XmlAttributes.key_addendum />
								</cfloop>
								<cfset "local.stCacheMap.#local.xmlData.Fusecache.Items.XmlChildren[a].XmlChildren[1].XmlText#.Variables" = local.arConditions />
							</cfif>
	
						</cfif>
						
					<cfelse>
						<!--- Found a duplicate setting.  Throw an error. --->
						<cfthrow type="fusecache.fusecache.duplicateItemKey" message="The Fusecache XML Configuration has multiple settings for the fuseaction #local.xmlData.Fusecache.Items.XmlChildren[a].XmlChildren[1].XmlText#" />
					</cfif>
					
	
				</cfloop>
			</cfloop>
		
			<cfcatch>
				<cfdump var="#cfcatch#"><cfabort>
			</cfcatch>
		</cftry>
		
		<cfset variables.instance.stCacheMap = local.stCacheMap />
		<cfreturn true />
		
	</cffunction>
	
	<!--- isDuplicateSetting() --->
	<cffunction name="isDuplicateSetting" access="private" output="false" returntype="boolean">
		<cfargument name="xmlData" type="any" required="true" />
		<cfargument name="sCircuit" type="string" required="true" />
		<cfargument name="sFuse" type="string" required="true" />
		
		<cfset var arSearchResult =  XmlSearch(Arguments.xmlData,"/fusecache/items/item[fuseaction='#Arguments.sCircuit#.#Arguments.sFuse#']") />
		<cfif ArrayLen(arSearchResult) GT 1>
			<cfreturn true />
		<cfelse>
			<cfreturn false />
		</cfif>
		
	</cffunction>
	
	
	<!--- getConfigFilePath() --->
	<cffunction name="getConfigFilePath" access="public" output="false" returntype="any" hint="Return the string path to the config file location.">
		<cfreturn variables.instance["sConfigFilePath"] />
	</cffunction>
	
	
	<!--- getConfig() --->
	<cffunction name="getConfig" access="public" output="false" returntype="any" hint="Returns the XML content (raw, non-processed) of the configuration file provided in the init() or refresh() call.">
		<cfreturn variables.instance["configFile"] />
	</cffunction>
	
	
	<!--- getCacheMap() --->
	<cffunction name="getCacheMap" access="public" output="false" returntype="struct" hint="Returns the configuration data formatted as a ColdFusion struct.">
		<cfreturn variables.instance["stCacheMap"] />
	</cffunction>
	
	
	<!--- getCacheEngine() --->
	<cffunction name="getCacheEngine" access="public" output="false" returntype="any" hint="Returns the cache engine object.">
		<cfreturn variables.instance["oCacheEngine"] />
	</cffunction>
	
	
	<!--- getStaticFileManager() --->
	<cffunction name="getStaticFileManager" access="public" output="false" returntype="any" hint="Returns the static file manager object.">
		<cfreturn variables.instance["oStaticFileManager"] />
	</cffunction>
	
	
	<!--- inDebugMode() --->
	<cffunction name="inDebugMode" access="public" output="false" returntype="any" hint="Returns true/false if we're in debug mode.">
		<cfreturn variables.instance["showDebug"] />
	</cffunction>
	
	
	<!--- doWriteStaticFiles() --->
	<cffunction name="doWriteStaticFiles" access="public" output="false" returntype="boolean" hint="Returns true/false if static file creation is on.">
		<cfreturn variables.instance["staticFileOutput"] />
	</cffunction>
	
	
	<!--- getStaticFileRootPath() --->
	<cffunction name="getStaticFileRootPath" access="public" output="false" returntype="any" hint="Returns true/false if static file creation is on.">
		<cfreturn variables.instance["staticFileOutputRoot"] />
	</cffunction>
	
	
	<!--- getDeleteAllStaticFilesOnApplicationEnd --->
	<cffunction name="getDeleteAllStaticFilesOnApplicationEnd" access="public" output="false" returntype="boolean" hint="Returns true/false if static file creation is on.">
		<cfreturn variables.instance["deleteAllStaticFilesOnApplicationEnd"] />
	</cffunction>
	
	
	<!--- getUseOutputFuse() --->
	<cffunction name="getUseOutputFuse" access="public" output="false" returntype="any" hint="Returns a boolean of whether this setup should use a seperate output fuse.">
		<cfreturn variables.instance["useOutputFuse"] />
	</cffunction>
	
	
	<!--- getOutputFuse() --->
	<cffunction name="getOutputFuse" access="public" output="false" returntype="any" hint="Returns the string name of the circuit to use for cached output.">
		<cfreturn variables.instance["outputFuse"] />
	</cffunction>
	
	
	<!--- getOutputVariableName() --->
	<cffunction name="getOutputVariableName" access="public" output="false" returntype="any" hint="Returns the string name of the content variable that cached data should be saved to.">
		<cfreturn variables.instance["outputVariable"] />
	</cffunction>
	
	
	<!--- getForceFuseboxCompletion() --->
	<cffunction name="getForceFuseboxCompletion" access="public" output="false" returntype="any" hint="Returns the boolean value saying whether or not to force Fusebox completion (which requires Fusebox core modifications).">
		<cfreturn variables.instance["forceFuseboxCompletion"] />
	</cffunction>
	
	
	<!--- getStaticFileManagementMode --->
	<cffunction name="getStaticFileManagementMode" access="public" output="false" returntype="any" hint="Returns string value that determines whether static file management is handled application by application or server by server.">
		<cfreturn variables.instance["staticFileManagementMode"] />
	</cffunction>
	
	
	<!--- getGlobalKeyPrefix --->
	<cffunction name="getGlobalKeyPrefix" access="public" output="false" returntype="any" hint="Returns the global key prefix.">
		<cfreturn variables.instance["sGlobalKeyPrefix"] />
	</cffunction>
	
	
	<!--- getFusecacheItem() --->
	<cffunction name="getFusecacheItem" access="public" output="false" returntype="fusecache.FusecacheItem" hint="Creates and returns a FusecacheItem object.">
		<cfargument name="sFuseaction" type="string" required="false" default="" />
		<cfscript>
			var local = {};
			local.oFusecacheItem = CreateObject("component","fusecache.FusecacheItem");
			if (arguments.sFuseaction EQ "") {
				local.oFusecacheItem.init(this);
			}
			else {
				local.oSettings = getSettings(arguments.sFuseaction);
				if (IsObject(local.oSettings)) {
					local.oFusecacheItem.load(this,local.oSettings);
				}
				else {
					local.oFusecacheItem.init(this);
				}
			}
			return local.oFusecacheItem;
		</cfscript>
	</cffunction>
	
	
	<!--- getRequestManager() --->
	<cffunction name="getRequestManager" access="public" output="false" returntype="fusecache.FusecacheRequestManager">
		<cfreturn CreateObject("component","fusecache.FusecacheRequestManager").init() />
	</cffunction>
	
	
	<!--- getSettings() --->
	<cffunction name="getSettings" access="public" output="false" returntype="any" hint="Searches the settings struct for data relating to the provided fuseaction. Returns Settings Object or false.">
		<cfargument name="sFuseaction" type="string" required="true" />
		<cfset var local = {} />
		<cfset local.oSettings = CreateObject("component","fusecache.FuseSettings").init() />
		
		<!--- Search the cacheMap keys for the first part of the fuseaction (circuit). --->
		<cfset local.arTopLevelKeySearch = StructFindKey(getCacheMap(),ListGetAt(arguments.sFuseaction,1,'.')) />
		<cfif (ArrayLen(local.arTopLevelKeySearch))>
			
			<!--- Found the circuit in the map. Check for the fuse. --->
			<cfset local.arSecondLevelKey = StructFindKey(Local.arTopLevelKeySearch[1].value,ListGetAt(Arguments.sFuseaction,2,'.')) />
			<cfif (ArrayLen(local.arSecondLevelKey))>
				
				<!--- Found the fuse. Return the settings object --->
				<cfset local.oSettings["Name"] = arguments.sFuseaction />
				<cfset local.oSettings["Key"] = local.arSecondLevelKey[1].value.key />
				<cfset local.oSettings["Expires"] = local.arSecondLevelKey[1].value.expires />
				<cfset local.oSettings["Type"] = local.arSecondLevelKey[1].value.type />
				
				<cftry>
					<cfset local.oSettings["Conditions"] = local.arSecondLevelKey[1].value.conditions />
					<cfcatch type="any"></cfcatch>
				</cftry>
				
				<cftry>
					<cfset local.oSettings["Variables"] = local.arSecondLevelKey[1].value.variables />
					<cfcatch type="any"></cfcatch>
				</cftry>
				
				<cftry>
					<cfset local.oSettings["staticfile"] = local.arSecondLevelKey[1].value.staticfile />
					<cfcatch type="any"></cfcatch>
				</cftry>

				<cfreturn local.oSettings />
			<cfelse>
				<cfreturn false />
			</cfif>
		<cfelse>
			<cfreturn false />
		</cfif>
	</cffunction>
	
</cfcomponent>